new
method or object
construct, several things happen that create and initialize the instance of that class. (The following sections on initialization apply equally to the new
method and the object
construct.) As shown in the figure, the new
method (1) allocates memory for the new object and sets up other internal values; it then (2) calls init
on the newly created instance to initialize it, passing along any arguments that had been specified to new
; it finally (3) calls afterInit
for post-initialization, again passing along any arguments that had been specified to new
. Notice that new
is a class method (operates on a class), while init
and afterInit
are instance methods. While its is routine to call the new
method, you should never directly
call init
or afterInit
(which are automatically called by new
); doing so
will not re-initialize the object, and might put the instance into an unknown
state or cause unexpected behavior. Calling init
, in particular, might cause
your program to crash.
This section describes init
and afterInit
, and how to override them in your own classes to specialize the initialization.
When you create a subclass of an existing class, you may often want to override how it is initialized. To do this, you override the init
and afterInit
methods in your subclass. You are not required to define either an init
or afterInit
method in your subclass; you only need to define them if your subclass's initialization behavior is different from that of its superclasses.
The new
method should almost never be overridden. (You could override new
if you want to limit the number of instances that can be created.)
The init
method initializes the new instance of its class. Every instantiable class has an init
method, which contains implementation to set up some of the initial values of a new instance.
Be aware that the instance is in an incomplete state until init
is done. This is very important to keep in mind. It is not safe to do anything that may have the side effect of calling a method on this instance, which is still in an incomplete state. Calling a method on an incomplete instance can cause your program to crash.
However, the afterInit
method is a safe place to perform all post-initialization work. Since the object is fully initialized at this point, it's safe to call a method on it. The default afterInit
method for most core classes is empty. The Collection
classes have an afterInit
method that allows you to add initial keys and values to that new collection. Where afterInit
comes in handy, again, is in subclasses you define.
If you create a subclass of Window
, you might define afterInit
to create whatever objects the window is supposed to initially requite or contain, such as pushbuttons, bitmaps, players, and other objects. Then every time new
is called on that class, the new window will automatically get the objects it needs.
Both init
and afterInit
are defined in similar ways and are described in the following sections.
keyword:value
The new
method for many classes uses keyword arguments. For example, the following code creates a new instance of a Point
class; it uses the keywords x
and y
to intialize the point to coordinates 10,20:
new Point x:10 y:20
Keyword arguments are described in the following section "Defining init Methods."
init
method; any method definition can contain keyword arguments.method init self #rest restArg [ #key keywordArgs ] -> (where:
. . .
apply nextMethod self keywordArgs restArg
. . .
)
args
) that holds the collection of keyword arguments passed into init
.
nextMethod
passes the original method call (including arguments) up the inheritance hierarchy, so that each superclass has a chance to perform its particular initialization on the new instance.
apply
calls nextMethod
with all members of restArg as arguments. For more information, see "Using apply to Call Functions" on page 104 in Chapter 5, "Functions, Threads and Pipes."
new
method automatically calls init
. When you call any function or method with a series of keyword arguments, these arguments are wrapped in a collection. In this case, the collection of keyword arguments are passed to restArg in init
.
Use #key
to define any new keyword arguments. An example of a keyword argument is scale:(10)
. Notice that the parentheses are required around the default value, even if the value is simply a number and not a complex expression. For a complete description of keyword arguments, see the discussion that begins on page 96 of Chapter 5, "Functions, Threads and Pipes."
If the new
method that calls init
does not contain a variable number of keyword arguments, then you do not need to include #rest
restArg. However, about the only time you can be sure of this constraint is with a non-subclassable (sealed) class that inherits directly from RootObject
.
Unless you really know what you're doing, there are only two things you can do inside the body of init
:
nextMethod
, optionally passing in keywords with initial values along to superclasses
nextMethod
, because init
is implemented in RootObject
; nextMethod
ensures that that implementation is called. Notice that it does not matter what init
returns; its return value is ignored.
Here is a "minimal" implementation of the init
method which does no specialization:
method init self #rest args -> (
apply nextMethod self args
)
In this example, any keywords supplied to the new
method are passed into init
as the collection args
; this collection is then passed up to the init
method of its superclasses using apply
nextMethod
.
init
definition:#key
line of init
. If the same keyword appears in restArg and keywordArgs, the method accepts only the first one. Therefore, since restArg appears before keywordArgs at this point, restArg takes precedence over keywordArgs.
nextMethod
in the body. Here the precedence order is opposite that of the previous case-since keywordArgs appears before restArg, keywordArgs takes precedence over restArg.
self.scale
:=
10
. If this occurs after nextMethod
, it takes precedence over the two previous places.
init
:
The BlahShape
class specializes Rect
to add an extra instance variable, which is initialized to a linked list.
class BlahShape (Rect)
inst vars
blahList
inst methods
method init self #rest args -> (
apply nextMethod self args
self.blahList := new LinkedList
return self
)
end
The TextTable
class, suitable for creating a table, specializes ArrayList
by adding three instance variables. It specializes the init
method to set values for those instance variables, supplying a default of 10 columns and a name of "Laura's Table". It also supplies a default value for initialSize
(one of the keyword arguments that ArrayList
defines) to set a TextTable instance to 30 elements.
class TextTable (ArrayList)
inst vars
recClass, columns, name
inst methods
method init self #rest args \
#key columns:(10) tabClass: name:("Laura's Table") -> (
apply nextMethod self initialSize:(30) args
self.recClass := tabClass
self.columns := columns
self.name := name
return self
)
end
ArrowPresenter
is a subclass of TwoDPresenter
that adds two instance variables and overrides by setting values for those instance variables. Its init
method also supplies a different default value for one of the keyword arguments that the TwoDPresenter
class uses.
class ArrowPresenter (TwoDPresenter)
instance variables strokePaint, direction
instance methods
method init self #rest args ->(
apply nextMethod self boundary:(new Rect x2:15 y2:15) args
self.strokePaint := blackBrush
self.direction := @up
return self
)
end
init
methods defined by superclasses, you can pass those values in keyword-value pairs using nextMethod
.
For example, if your superclass's init
method defines a scale
keyword argument, but you want instances of your class to always have a scale of 15
, you could supply the value 15
to the scale
keyword in the nextMethod
call.
-- Initial value of scale is always 15, even if passed in another value
method init self #rest args -> (
apply nextMethod self scale:15 args
return self
)
Now, even if an alternate value for scale
is supplied when this method is called, a scale of 15
is always used. Both values appear in the nextMethod
arguments (one explicitly as scale
, the other is in args
), but nextMethod
always chooses the first value of the key it finds.
In addition, a subtle point with the init
method (or with any function or method that defines both rest and keyword arguments) is that the arg
argument collects only keyword arguments that have been supplied directly in the call to init
; it does not collect keyword arguments for which you've simply specified default values in the init
definition.
For example, say that your superclass takes an optional scale
keyword. Therefore, if you want to be able to override the default value, you might assume that simply specifying a default value to the keyword argumenta after #key
will work:
-- Incorrect implementation
-- This method does not pass the default scale value to its superclasses
method init self #rest args #key scale:(15) -> (
apply nextMethod self args
)
The problem in this example is that unless you specifically give a value for scale
when you create an instance of your class, the value of scale
does not get appended to args
, and so does not automatically get passed to your superclass's init
method.
To pass the default value of any keyword argument you define in your own init
method to your superclass's init
, you must explicitly pass that keyword along in the nextMethod
call, and supply its current value:
-- Correct implementation
-- This method passes the default value of scale to its superclasses
method init self #rest args #key scale:(15) -> (
apply nextMethod self scale:scale args
)
This allows you to specify the default value of 15
to the scale
keyword argument. It also allows the value of scale
to be overridden by a value supplied with new
or object
.
afterInit
method is identical in structure to the init
method. Just as with init
, the afterInit
method requires a rest argument, and you must call apply
nextMethod
at some point in the body of afterInit
so that all superclasses have a chance to add their post-initialization behavior.
However, it is much less restrictive than init
in what you can implement inside the body, since self at this point is fully initialized.
method afterInit self #rest restArg [ #key keywordArgs ] -> (See the previous init method definition for descriptions of restArg, keywordArgs, and the other elements of this definition.
. . .
apply nextMethod self keywordArgs restArg
. . .
)
The following is a simple example using init
and afterInit
. The class SameKey
inherits from SortedKeyedArray
, but for every key-value pair in the array, each key is exactly the same object. Only the values vary. The SameKey
class has an instance variable called, appropriately, key
, which holds the key that is repeated for every key-value pair. When you call new
on this class, you have two available keyword arguments: key
, which specifies the value for the key
instance variable, and vals
, which holds an array of potential values for the new collection. During initialization of the new object, the value of key
is assigned to the key
instance variable, and the contents of vals
are added to the collection.
class SameKey (SortedKeyedArray)
instance vars
key
instance methods
method init self #rest args #key key: -> (
apply nextMethod self args
self.key := key
)
method afterInit self #rest args #key vals: -> (
apply nextMethod self args
for i in vals do add self self.key i
)
endThe
init
method defines a keyword argument, key
, to hold that key, and assigns that value to the instance variable self.key
. Because adding a new element to a collection requires a method call on that object, an afterInit
method is required. The afterInit
method defines an additional keyword argument, vals
, which holds an array of possible values for the new class. The afterInit
method then adds the new key-value pair (using the key specified in the key
instance variable) to the new collection.
When a new instance of SameKey
is created, it looks like this:
newSameKey := new SameKey key:@flavor \
vals:#(@cherry,@watermelon,@peach)
#(@flavor:@peach, @flavor:@watermelon, @flavor:@cherry) as SameKey
This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.